package com.summer.bilibili.aop.repeat;

import cn.hutool.crypto.digest.DigestUtil;
import com.summer.bilibili.util.SecurityUtils;
import com.summer.bilibili.exception.CustomException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import com.summer.bilibili.util.RedisUtils;
import com.summer.bilibili.util.JsonUtils;
import com.summer.bilibili.consts.Code;


import javax.annotation.Resource;

/**
 * 创建切面类防止用户重复提交
 */
@Aspect
@Component
public class RepeatSubmitAspect {

    @Resource
    private RedisUtils redisUtils;


    @Pointcut("@annotation(com.summer.bilibili.aop.repeat.RepeatSubmit)")
    public void pointCut () {
    }


    @Around("pointCut()")
    public Object around ( ProceedingJoinPoint joinPoint ) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature ( );
        RepeatSubmit repeatSubmit = signature.getMethod ( ).getAnnotation ( RepeatSubmit.class );
        Long contextUserId = SecurityUtils.getContextUserId ( false ); // 获取当前用户ID，未登录返回 null
        if ( repeatSubmit == null || repeatSubmit.disable ( ) || contextUserId == null ) {
            // 满足任一放行 1. 无注解 2. 禁用 3. 未登录
            return joinPoint.proceed ( );
        }

        //方法全局唯一标记，完整方法签名
        String methodPath = signature.toLongString ( );
        //方法的参数,包括params和body 序列化
        StringBuilder methodArgs = new StringBuilder ( );
        for ( Object arg : joinPoint.getArgs ( ) ) {
            if ( arg != null ) {
                methodArgs.append ( JsonUtils.writeString ( arg ) );
            }
        }
        //参数会非常长,必须缩减到固定长度
        String key = "REPEAT:" + DigestUtil.md5Hex ( contextUserId + methodPath + methodArgs );


        //如果不存在才会设置成功
        boolean success = redisUtils.setNX ( key , System.currentTimeMillis ( ) , repeatSubmit.interval ( ) );
        if ( !success ) {
            throw new CustomException ( Code.REPEAT_SUBMIT );
        }
        //执行目标方法
        try {
            return joinPoint.proceed ( ); // 执行原方法
        } catch (Exception e) {
            //如果目标方法执行失败.则不记录
            redisUtils.del ( key );
            throw e;
        }
    }
}
